#include <iostream>
#include <string>
#include <memory>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<cstring>
enum ExitCode
{
    UsageErr = 1,
    SocketErr,
    ConnectErr,

};

enum class Status
{
    NEW, // 要建立新连接
    CONNECTING,
    CONNECTED,    // 建立好
    RECONNECT, // 需重新连接
    CLOSED        // 关闭连接
};
const static int defaultfd = -1;
const static int defaultinterval = 1;
const static int defaultnum = 5;

class ClientConnect
{
public:
    ClientConnect(const std::string &ip, const uint16_t port)
        : _socketfd(defaultfd),
          _serverip(ip),
          _serverport(port),
          _status(Status::NEW),
          _retry_interval(defaultinterval),
          _retry_num(defaultnum)
    {
    }
    void GetNewConnect()
    {
        _socketfd=socket(AF_INET,SOCK_STREAM,0);
        if(_socketfd<0)
        {
            std::cerr<<"create socketfd error"<<std::endl;
            exit(ExitCode::SocketErr);
        }
        struct sockaddr_in serverwhat;
        bzero(&serverwhat,sizeof(serverwhat));
        serverwhat.sin_family=AF_INET;
        serverwhat.sin_port=htons(_serverport);
        serverwhat.sin_addr.s_addr=inet_addr(_serverip.c_str());
        int n=connect(_socketfd,(struct sockaddr*)&serverwhat,sizeof(serverwhat));
        if(n<0)
        {
            std::cerr<<"connect error"<<std::endl;
            NeedClose();
            _status=Status::RECONNECT;
            // exit(ExitCode::ConnectErr);
            return;
        }
        _status=Status::CONNECTED;
    }
    void DoingWork()
    {
        while(true)
        {
            std::string message="hello server,I am Client";
            ::send(_socketfd,message.c_str(),message.size(),0);
            char buffer[1024];
            int tmp=::recv(_socketfd,buffer,sizeof(buffer)-1,0);
            if(tmp<=0)
            {
                std::cout<<"connect broken,try to reconnect"<<std::endl;
                NeedClose();
                _status=Status::RECONNECT;
                return;
            }
            buffer[tmp]=0;
            std::cout<<"Server Echo:"<<buffer<<std::endl;
            sleep(1);
        }
    }
    void NeedReconnect()
    {
        int cnt=0;
        while(true)
        {
            cnt++;
            std::cout<<"try to reconnect,count is: "<<cnt<<"......"<<std::endl;
            GetNewConnect();
            if(_status==Status::CONNECTED)
            {
                std::cout<<"reconnect success"<<std::endl;
                return;
            }
            if(cnt>=_retry_num)
            {
                std::cout<<"can't reconnect,client quit"<<std::endl;
                _status=Status::CLOSED;
                return;
            }
            sleep(_retry_interval);
        }
    }
    void NeedClose()
    {
        close(_socketfd);
        _socketfd=defaultfd;    
    }
    Status ConnectStatus()
    {
        return _status;
    }

private:
    int _socketfd;
    std::string _serverip;
    uint16_t _serverport;
    Status _status;
    int _retry_interval;
    int _retry_num;
};

class TcpClient
{
public:
    TcpClient(const std::string &ip, const uint16_t port)
        : _cc(ip, port)
    {
    }

    void StartClient()
    {
        std::cout<<"Start Client"<<std::endl;
        while(true)
        {
            Status tmpst=_cc.ConnectStatus();
            switch(tmpst)
            {
                case Status::NEW:
                _cc.GetNewConnect();
                break;
                case Status::CONNECTED:
                _cc.DoingWork();
                break;
                case Status::RECONNECT:
                _cc.NeedReconnect();
                break;
                case Status::CLOSED://重连失败后，关文件描述符，要退出
                _cc.NeedClose();
                std::cout<<"End Client"<<std::endl;
                return;
                break;
                default:
                std::cerr<<"No This Status"<<std::endl;
                break;

            }

        }
    }

private:
    ClientConnect _cc;
};
void Usage(std::string s)
{
    std::cerr << "please enter: " << s << " serverip serverport" << std::endl;
}
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(ExitCode::UsageErr);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);
    std::unique_ptr<TcpClient> uptr = std::make_unique<TcpClient>(serverip, serverport);
    uptr->StartClient();

    return 0;
}